#pragma once

#include <atomic>
#include <mutex>
#include <condition_variable>
#include "../base/types.h"

OPEN_JLIB_NS


struct SpinLock {

  inline void lock () volatile noexcept {
    while (_flag.test_and_set(std::memory_order_acquire));
  }

  inline void unlock () volatile noexcept {
    _flag.clear(std::memory_order_release);
  }

  inline void open () noexcept {
    lock();
  }

  inline void close () noexcept {
    unlock();
  }

private:
  std::atomic_flag _flag = ATOMIC_FLAG_INIT;
};



struct WriteFirstLock {

  struct ReadLock {
    inline ReadLock (WriteFirstLock &lock) noexcept : _lock(lock) {}
    inline void open () noexcept { _lock.lockForRead(); }
    inline void close () noexcept { _lock.unlockForRead(); }
  private:
    WriteFirstLock& _lock;
  };

  struct WriteLock {
    inline WriteLock (WriteFirstLock &lock) noexcept : _lock(lock) {}
    inline void open () noexcept { _lock.lockForWrite(); }
    inline void close () noexcept { _lock.unlockForWrite(); }
  private:
    WriteFirstLock& _lock;
  };


  void lockForRead () noexcept {
    std::unique_lock<std::mutex> lock(_mutex);
    while (_writers > 0)
      _read_condition.wait(lock);
    ++_readers;
  }

  void unlockForRead () noexcept {
    std::unique_lock<std::mutex> lock(_mutex);
    if (--_readers == 0 && _writers > 0)
      _write_condition.notify_one();
  }

  void lockForWrite () noexcept {
    std::unique_lock<std::mutex> lock(_mutex);
    ++_writers;
    while (_readers || _writing)
      _write_condition.wait(lock);
    _writing = true;
  }

  void unlockForWrite () noexcept {
    std::unique_lock<std::mutex> lock(_mutex);
    _writing = false;
    if (--_writers > 0)
      _write_condition.notify_one();
    else if (_readers > 0)
      _read_condition.notify_all();
  }

  inline ReadLock readLock () noexcept {
    return {*this};
  }

  inline WriteLock writeLock () noexcept {
    return {*this};
  }

private:
  std::mutex _mutex;
  std::condition_variable _read_condition;
  std::condition_variable _write_condition;
  volatile unsigned long _readers {0};
  volatile unsigned long _writers {0};
  volatile bool _writing {false};
};



struct ReadFirstLock {

  struct ReadLock {
    inline ReadLock (ReadFirstLock &lock) noexcept : _lock(lock) {}
    inline void open () noexcept { _lock.lockForRead(); }
    inline void close () noexcept { _lock.unlockForRead(); }
  private:
    ReadFirstLock& _lock;
  };

  struct WriteLock {
    inline WriteLock (ReadFirstLock &lock) noexcept : _lock(lock) {}
    inline void open () noexcept { _lock.lockForWrite(); }
    inline void close () noexcept { _lock.unlockForWrite(); }
  private:
    ReadFirstLock& _lock;
  };

  void lockForRead () noexcept {
    std::unique_lock<std::mutex> lock(_mutex);
    ++_readers;
    while (_writing)
      _read_condition.wait(lock);
  }

  void unlockForRead () noexcept {
    std::unique_lock<std::mutex> lock(_mutex);
    if (--_readers == 0 && _writers > 0)
      _write_condition.notify_one();
  }

  void lockForWrite () noexcept {
    std::unique_lock<std::mutex> lock(_mutex);
    ++_writers;
    while (_readers || _writing)
      _write_condition.wait(lock);
    _writing = true;
  }

  void unlockForWrite () noexcept {
    std::unique_lock<std::mutex> lock(_mutex);
    _writing = false;
    --_writers;
    if (_readers > 0)
      _read_condition.notify_all();
    else if (_writers > 0)
      _write_condition.notify_one();
  }

  inline ReadLock readLock () noexcept {
    return {*this};
  }

  inline WriteLock writeLock () noexcept {
    return {*this};
  }

private:
  std::mutex _mutex;
  std::condition_variable _read_condition;
  std::condition_variable _write_condition;
  volatile unsigned long _readers {0};
  volatile unsigned long _writers {0};
  volatile bool _writing {false};
};


CLOSE_JLIB_NS